package com.audaque.springboot.foshanupload.web.webdemo.controller;

import com.audaque.springboot.foshanupload.web.webdemo.service.impl.HelloServiceImpl;
import com.audaque.springboot.foshanupload.web.webdemo.service.inface.ICflow;
import com.audaque.springboot.foshanupload.web.webdemo.service.inface.IHei;
import com.audaque.springboot.foshanupload.web.webdemo.service.inface.IHello;
import com.fasterxml.jackson.databind.ObjectMapper;
import javassist.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.lang.reflect.Method;

@Slf4j
@RestController
@RequestMapping("javassist")
public class JavassistController {


    @GetMapping("/loadClass")
    public void loadClass() throws Exception {
        ClassPool pool = new ClassPool(true);
        /**
         * 在Java中，多个类加载器可以共存，每个类加载器创建自己的名称空间。不同的类加载器可以加载具有相同类名的不同类文件。
         * 加载的两个类被视为不同的类。此功能使我们能够在单个 JVM 上运行多个应用程序，即使这些程序包含具有相同名称的不同的类。
         * 如果相同的类文件由两个不同的类加载器加载，则 JVM 会创建两个具有相同名称和定义的不同的类。
         * 由于两个类不相同，一个类的实例不能被分配给另一个类的变量。两个类之间的转换操作将失败并抛出一个 ClassCastException。
         *
         */
        //类加载器1
        Loader loader = new Loader(pool);
        Class cl = loader.loadClass("com.audaque.springboot.foshanupload.web.webdemo.service.impl.HelloServiceImpl");

        //实现类HelloImpl
        //java.lang.ClassCastException: class com.audaque.springboot.foshanupload.web.webdemo.service.impl.HelloServiceImpl cannot be cast to class com.audaque.springboot.foshanupload.web.webdemo.service.inface.IHello (
        // com.audaque.springboot.foshanupload.web.webdemo.service.impl.HelloServiceImpl is in unnamed module of loader javassist.Loader @470a3e6c;
        // com.audaque.springboot.foshanupload.web.webdemo.service.inface.IHello is in unnamed module of loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader @3da78849)
        //IHello hello = (IHello) cl.newInstance();
        // hello.sayHello("javassist");
        Object o = cl.newInstance();

        Method m = cl.getMethod("sayHello", String.class);
        //静态方法属于类，不属于对象，传递进去的对象参数并没有应用，所以使用null即可
         m.invoke(o, "javassist");
        //类加载器2
        HelloServiceImpl helloService = new HelloServiceImpl();

        helloService.sayHello("javassist");
        //类加载器3
        CtClass ctClass = pool.get("com.audaque.springboot.foshanupload.web.webdemo.service.impl.HelloServiceImpl");
        Class<?> aClass = ctClass.toClass();
        Object o1 = aClass.newInstance();

        Method m1 = aClass.getMethod("sayHello", String.class);
        //静态方法属于类，不属于对象，传递进去的对象参数并没有应用，所以使用null即可
        m1.invoke(o1, "javassist");
        //类加载器4
        ClassPool pool4 = ClassPool.getDefault();

        CtClass ctClass4 = pool4.get("com.audaque.springboot.foshanupload.web.webdemo.service.impl.HelloServiceImpl");
        Class<?> aClass4 = ctClass4.toClass();
        Object o4 = aClass4.newInstance();

        Method m4 = aClass4.getMethod("sayHello", String.class);
        //静态方法属于类，不属于对象，传递进去的对象参数并没有应用，所以使用null即可
        m4.invoke(o4, "javassist");
    }


    @GetMapping("/toClass")
    public void toClass() throws Exception {
        ClassPool pool = new ClassPool(true);
        //已存在文件，所以从直接获取
        CtClass targetClass = pool.get("com.audaque.springboot.foshanupload.web.webdemo.service.impl.HelloServiceImpl");

        // 编译，注意：如果类已经加载了，是不能重定义的，会报错 duplicate class definition....
        Class<?> helloClass = targetClass.toClass();
        //实现类HelloImpl
        IHello hello = (IHello) helloClass.newInstance();
        hello.sayHello("javassist");


        //解冻
        targetClass.defrost();


        CtMethod method = targetClass.getDeclaredMethod("sayHello");
        // 在方法体前插入代码块
        method.insertBefore("System.out.println(\"before hello \" + message);");
        // 在方法体后插入代码块
        method.insertAfter("{System.out.println(\"after hello \" + message);}");

        /**
         * JVM 不允许动态重新加载类。一旦类加载器加载了一个类，它不能在运行时重新加载该类的修改版本。因此，在JVM 加载类之后，你不能更改类的定义。
         */
        // 编译，注意：如果类已经加载了，是不能重定义的，会报错 duplicate class definition....
        //loader org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader @4c6d8eee (instance of org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader, child of org.springframework.boot.devtools.restart.classloader.RestartClassLoader @3023f62e org.springframework.boot.devtools.restart.classloader.RestartClassLoader) attempted duplicate class definition for com.audaque.springboot.foshanupload.web.webdemo.service.impl.HelloServiceImpl.
        Class<?> helloClass1 = targetClass.toClass();
        //实现类HelloImpl
        IHello hello1 = (IHello) helloClass1.newInstance();
        hello1.sayHello("javassist");


    }


    @GetMapping("/defineMethod")
    public void defineMethod() throws Exception {
        ClassPool pool = new ClassPool(true);
        // 不存在文件，所以要直接创建， IHello 的实现类
        CtClass newClass = pool.makeClass("com.audaque.springboot.foshanupload.web.webdemo.service.impl.HelloImpl");
        // 添加接口
        newClass.addInterface(pool.get(IHello.class.getName()));
        // 返回类型 Void
        CtClass returnType = pool.get(void.class.getName());
        // 参数
        CtClass[] parameters = new CtClass[]{pool.get(String.class.getName())};
        // 定义方法
        CtMethod method = new CtMethod(returnType, "sayHello", parameters, newClass);
        // 方法体代码块，必须用 {} 包裹代码
        String source = "{" +
                "System.out.println(\"hello \" + $1);"
                + "}";
        // 设置方法体
        method.setBody(source);
        // 添加方法
        newClass.addMethod(method);
        // 编译、转换成 Class 字节码对象
        Class helloClass = newClass.toClass();

        //实现类HelloImpl
        IHello hello = (IHello) helloClass.newInstance();
        hello.sayHello("javassist");




    }

    /**
     * Javassist 不允许删除方法或字段，但它允许更改名称。
     * Javassist 不允许向现有方法添加额外的参数。你可以通过新建一个方法达到同样的效果。
     *
     * @throws Exception
     */
    @GetMapping("/overrideMethod")
    public void overrideMethod() throws Exception {
        ClassPool pool = new ClassPool(true);
        //已存在文件，所以从直接获取
        CtClass targetClass = pool.get("com.audaque.springboot.foshanupload.web.webdemo.service.impl.HelloServiceImpl");
        CtMethod method = targetClass.getDeclaredMethod("sayHello");

        // 复制方法生成一个新的方法
        CtMethod agentMethod = CtNewMethod.copy(method, method.getName() + "$agent", targetClass, null);
        //限定符
        agentMethod.setModifiers(Modifier.PRIVATE);
        // 添加方法
        targetClass.addMethod(agentMethod);
        // 构建新的方法体
        String source = "{"
                + "System.out.println(\"before handle >  ...\" + $type);"
                + method.getName() + "$agent($$);"
                + "System.out.println(\"after handle ...\");"
                + "}";
        // 设置方法体，覆盖旧的
        method.setBody(source);
        // 编译，注意：如果类已经加载了，是不能重定义的，会报错 duplicate class definition....
        Class<?> helloClass = targetClass.toClass();
        //实现类HelloImpl
        IHello hello = (IHello) helloClass.newInstance();
        hello.sayHello("javassist");

    }

    @GetMapping("/getMethodSignature")
    public String getMethodSignature() throws Exception {
        // 获取 ClassPool 对象，使用系统默认类路径
        ClassPool pool = new ClassPool(true);
        CtClass ctClass5 = pool.get(ObjectMapper.class.getName());
        CtMethod ctMethod = ctClass5.getDeclaredMethod("_readMapAndClose");
        // 方法签名 格式：(Ljava/lang/String;Ljava/util/List;Lcom/test/Order;)Ljava/lang/Integer;
        return ctMethod.getSignature();
    }


    @GetMapping("/aspectMethod")
    public void aspectMethod() throws Exception {
        ClassPool pool = new ClassPool(true);
        //已存在文件，所以从直接获取
        CtClass targetClass = pool.get("com.audaque.springboot.foshanupload.web.webdemo.service.impl.HelloServiceImpl");
        CtMethod method = targetClass.getDeclaredMethod("sayHello");
        // 在方法体前插入代码块
        method.insertBefore("System.out.println(\"before hello \" + message);");
        // 在方法体后插入代码块
        method.insertAfter("{System.out.println(\"after hello \" + message);}");
        // 编译，注意：如果类已经加载了，是不能重定义的，会报错 duplicate class definition....
        Class<?> helloClass = targetClass.toClass();
        //实现类HelloImpl
        IHello hello = (IHello) helloClass.newInstance();
        hello.sayHello("javassist");
        targetClass.writeFile();

    }


    @GetMapping("/var")
    public CtClass var() throws Exception {
        ClassPool pool = new ClassPool(true);
        //已存在文件，所以从直接获取
        CtClass targetClass = pool.get("com.audaque.springboot.foshanupload.web.webdemo.service.impl.HeiServiceImpl");
        CtMethod method = targetClass.getDeclaredMethod("sayHei");
        //添加方法
        CtMethod newMethod = CtNewMethod.make("public String print(String a) { return a; }", targetClass);
        targetClass.addMethod(newMethod);
        // 在方法体后插入代码块
        method.insertAfter("{" +
                "        System.out.println(\"$0:\"+$0);\n" +
                "        System.out.println(\"$1:\"+$1);\n" +
                "        System.out.println(\"$args:\"+$args);\n" +
                "        System.out.println(\"$_:\"+$_);\n" +
                "        System.out.println(\"$sig:\"+$sig);\n" +
                "        System.out.println(\"$type:\"+$type);\n" +
                "        System.out.println(\"print($$):\"+print($$));\n" +

                "}");
        // 编译，注意：如果类已经加载了，是不能重定义的，会报错 duplicate class definition....
        Class<?> heiClass = targetClass.toClass();
        //实现类HelloImpl
        IHei hei = (IHei) heiClass.newInstance();
        String result = hei.sayHei("javassist");
        System.out.println("result：" + result);
        //写到文件里
        targetClass.writeFile();
        return targetClass;
    }

    @GetMapping("/writeFile")
    public CtClass writeFile() throws Exception {
        ClassPool pool = new ClassPool(true);
        //已存在文件，所以从直接获取
        CtClass targetClass = pool.get("com.audaque.springboot.foshanupload.web.webdemo.service.impl.HeiServiceImpl");
        CtMethod method = targetClass.getDeclaredMethod("sayHei");
        //添加方法
        CtMethod newMethod = CtNewMethod.make("public String print(String a) { return a; }", targetClass);
        targetClass.addMethod(newMethod);
        // 在方法体后插入代码块
        method.insertAfter("{" +
                "        System.out.println(\"$0:\"+$0);\n" +
                "        System.out.println(\"$1:\"+$1);\n" +
                "        System.out.println(\"$args:\"+$args);\n" +
                "        System.out.println(\"$_:\"+$_);\n" +
                "        System.out.println(\"$sig:\"+$sig);\n" +
                "        System.out.println(\"$type:\"+$type);\n" +
                "        System.out.println(\"print($$):\"+print($$));\n" +

                "}");
        //写到文件里
        targetClass.writeFile();
        return targetClass;
    }

    @GetMapping("/defrost")
    public void defrost() throws Exception {
        CtClass targetClass = writeFile();
        //解冻
        targetClass.defrost();
        //解冻后就可以修改
        CtMethod method = targetClass.getDeclaredMethod("sayHei");
        // 在方法体后插入代码块
        method.insertBefore("{" +
                "        System.out.println(\"$0:\"+$0);\n" +
                "        System.out.println(\"$1:\"+$1);\n" +
                "        System.out.println(\"$args:\"+$args);\n" +
                "        System.out.println(\"-----------------------------------------------------------------\");\n" +
                "}");
        // 编译，注意：如果类已经加载了，是不能重定义的，会报错 duplicate class definition....
        Class<?> heiClass = targetClass.toClass();
        //实现类HelloImpl
        IHei hei = (IHei) heiClass.newInstance();
        String result = hei.sayHei("javassist");
        System.out.println("result：" + result);
        //写到文件里
        targetClass.writeFile();
    }

    @GetMapping("/stopPruning")
    public void stopPruning() throws Exception {
        ClassPool pool = new ClassPool(true);
        //已存在文件，所以从直接获取
        CtClass targetClass = pool.get("com.audaque.springboot.foshanupload.web.webdemo.service.impl.HeiServiceImpl");
        CtMethod method = targetClass.getDeclaredMethod("sayHei");
        //添加方法
        CtMethod newMethod = CtNewMethod.make("public String print(String a) { return a; }", targetClass);
        targetClass.addMethod(newMethod);
        // 在方法体后插入代码块
        method.insertAfter("{" +
                "        System.out.println(\"$0:\"+$0);\n" +
                "        System.out.println(\"$1:\"+$1);\n" +
                "        System.out.println(\"$args:\"+$args);\n" +
                "        System.out.println(\"$_:\"+$_);\n" +
                "        System.out.println(\"$sig:\"+$sig);\n" +
                "        System.out.println(\"$type:\"+$type);\n" +
                "        System.out.println(\"print($$):\"+print($$));\n" +

                "}");
        //驳回修改
        targetClass.stopPruning(true);
        // 编译，注意：如果类已经加载了，是不能重定义的，会报错 duplicate class definition....
        Class<?> heiClass = targetClass.toClass();
        //实现类HelloImpl
        IHei hei = (IHei) heiClass.newInstance();
        String result = hei.sayHei("javassist");
        System.out.println("result：" + result);
        //写到文件里
        targetClass.writeFile();
    }


    //$cflow 的全名为：control flow，这是一个只读变量，返回指定方法递归调用的深度。
    @GetMapping("/cflow")
    public void cflow() throws Exception {
        ClassPool pool = new ClassPool(true);
        // 不存在文件，所以要直接创建， IHello 的实现类
        CtClass newClass = pool.makeClass("com.audaque.springboot.foshanupload.web.webdemo.service.impl.CflowImpl");
        // 添加接口
        newClass.addInterface(pool.get(ICflow.class.getName()));
        // 返回类型 int
        CtClass returnType = pool.get(int.class.getName());
        // 参数
        CtClass[] parameters = new CtClass[]{pool.get(int.class.getName())};
        // 定义方法
        CtMethod method = new CtMethod(returnType, "f", parameters, newClass);
        // 方法体代码块，必须用 {} 包裹代码
        String source = "{" +
                "if($1 <= 1){" +
                "     return $1;" +
                "}" +
                "return f($1 - 1) + f( $1 - 2);" +
                "}";
        // 设置方法体
        method.setBody(source);
        // 添加方法
        newClass.addMethod(method);
        // 编译、转换成 Class 字节码对象
        Class cflowClass = newClass.toClass();
        //实现类HelloImpl
        ICflow iCflow = (ICflow) cflowClass.newInstance();
        int result1 = iCflow.f(1);
        System.out.println("result1：" + result1);
        int result2 = iCflow.f(2);
        System.out.println("result2：" + result2);
        int result3 = iCflow.f(3);
        System.out.println("result3：" + result3);
        int result4 = iCflow.f(4);
        System.out.println("result4：" + result4);
        int result5 = iCflow.f(5);
        System.out.println("result5：" + result5);
    }


}
